// 三维几何体
// 数据流：顶点数据（positions） → BufferAttribute → geometry.setAttribute → Mesh → 场景
import { useEffect } from 'react';
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';

function App() {
  useEffect(() => {
    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(
      75,
      window.innerWidth / window.innerHeight,
      0.1,
      1000
    );
    camera.position.set(0, 0, 10); // 相机位置
    scene.add(camera);

    // 渲染器
    const myrender = new THREE.WebGLRenderer();
    myrender.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(myrender.domElement);

    // 坐标轴
    const axesHelper = new THREE.AxesHelper(5);
    scene.add(axesHelper);
    const temp = new THREE.Vector3();

    // 光源
    const light = new THREE.DirectionalLight(0xffffff, 1);
    light.position.set(1, 1, 1);
    scene.add(light);

    // 交互
    const controls = new OrbitControls(camera, myrender.domElement);
    controls.enableDamping = true; // 启用阻尼效果

    function makeSpherePositions(segmentsAround, segmentsDown) {
      const numVertices = segmentsAround * segmentsDown * 6; // 顶点数= 纬分段数*经分段数*6
      const numComponents = 3; // 顶点分量定义
      const positions = new Float32Array(numVertices * numComponents); // 顶点数组
      const indices = []; // 顶点索引缓冲，定义了如何将顶点连接成三角形面片

      // 通过创建三个嵌套的 Object3D 对象来模拟经纬度坐标系
      const longHelper = new THREE.Object3D(); // 控制经度旋转
      const latHelper = new THREE.Object3D(); // 控制纬度旋转
      const pointHelper = new THREE.Object3D(); // 实际点
      pointHelper.position.z = 1; // 初始位置在z轴
      longHelper.add(latHelper); // 经度是父级
      latHelper.add(pointHelper); // 纬度子级
      const temp = new THREE.Vector3();

      // 计算并返回旋转后的点在三维空间中的坐标
      function getPoint(lat, long) {
        latHelper.rotation.x = lat; // 将latHelper（及其子对象）绕X轴旋转lat
        longHelper.rotation.y = long;
        longHelper.updateMatrixWorld(true); // 更新
        return pointHelper.getWorldPosition(temp).toArray(); // 计算 pointHelper 在全局坐标系中的位置，返回[x, y, z]数组
      }

      let posNdx = 0;
      let ndx = 0;
      for (let down = 0; down < segmentsDown; ++down) {
        // 单位化纬度
        const v0 = down / segmentsDown; // 当前分段索引/纬度方向总分段数
        const v1 = (down + 1) / segmentsDown;
        // 转为实际角度
        const lat0 = (v0 - 0.5) * Math.PI;
        const lat1 = (v1 - 0.5) * Math.PI;

        for (let across = 0; across < segmentsAround; ++across) {
          // 单位化经度（0-1）
          const u0 = across / segmentsAround;
          const u1 = (across + 1) / segmentsAround;
          // 转为实际经度（0-2π）
          const long0 = u0 * Math.PI * 2;
          const long1 = u1 * Math.PI * 2;

          // 当前四边形的四个角顶点坐标
          positions.set(getPoint(lat0, long0), posNdx); // 左下角
          posNdx += numComponents;
          positions.set(getPoint(lat1, long0), posNdx); // 右下角
          posNdx += numComponents;
          positions.set(getPoint(lat0, long1), posNdx); // 左上角
          posNdx += numComponents;
          positions.set(getPoint(lat1, long1), posNdx); // 右上角
          posNdx += numComponents;

          indices.push(ndx, ndx + 1, ndx + 2, ndx + 2, ndx + 1, ndx + 3); // 三角形索引
          ndx += 4;
        }
      }
      return { positions, indices }; // 返回顶点坐标和索引
    }
    const segmentsAround = 24; //经分段数
    const segmentsDown = 16; //纬分段数
    const { positions, indices } = makeSpherePositions(
      segmentsAround,
      segmentsDown
    );
    const normals = positions.slice(); // 法线，slice() 创建了这个数组的一个副本

    // 创建几何体
    const geometry = new THREE.BufferGeometry();
    const positionNumComponents = 3; //每个顶点有 3 个分量（x, y, z）
    const normalNumComponents = 3; // 每个法线有 3 个分量（nx, ny, nz）
    // 创建一个顶点属性（BufferAttribute），管理顶点坐标数据。
    const positionAttribute = new THREE.BufferAttribute(
      positions, // 顶点坐标数组
      positionNumComponents // 顶点的分量
    );
    // 添加到场景
    const material = new THREE.MeshBasicMaterial({
      color: 0x00ff00,
      wireframe: true, // 线框
    });
    const sphere = new THREE.Mesh(geometry, material);
    scene.add(sphere);

    positionAttribute.setUsage(THREE.DynamicDrawUsage); // DynamicDrawUsage 表示数据会多次更改（适合动画场景

    // 绑定顶点坐标、法线、索引（三角形如何连接--一个四边形由两个三角形构成，共用2个顶点）
    geometry.setAttribute('position', positionAttribute);
    geometry.setAttribute(
      'normal', // 法线用于光照计算
      new THREE.BufferAttribute(normals, normalNumComponents) // 法线数组，每个法线分量数
    );
    geometry.setIndex(indices);

    positionAttribute.needsUpdate = true; // 标记数据需要更新,强制 Three.js 重新上传数据到 GPU

    // 渲染
    let time = Date.now();
    function renders() {
      time += 0.01;
      // 动态修改顶点位置
      for (let i = 0; i < positions.length; i += 3) {
        const quad = (i / 12) | 0;
        const ringId = (quad / segmentsAround) | 0;
        const ringQuadId = quad % segmentsAround;
        const ringU = ringQuadId / segmentsAround;
        const angle = ringU * Math.PI * 2;
        temp.fromArray(normals, i);
        temp.multiplyScalar(
          THREE.MathUtils.lerp(
            1,
            1.4,
            Math.sin(time + ringId + angle) * 0.5 + 0.5
          )
        );
        temp.toArray(positions, i);
      }
      myrender.render(scene, camera);
      requestAnimationFrame(renders);
    }
    renders();
  }, []);
  return <div></div>;
}
export default App;
